package com.workhours.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.workhours.dto.StatisticsRequest;
import com.workhours.entity.Holiday;
import com.workhours.entity.RestRecord;
import com.workhours.entity.User;
import com.workhours.mapper.HolidayMapper;
import com.workhours.mapper.RestRecordMapper;
import com.workhours.mapper.UserMapper;
import com.workhours.vo.StatisticsVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 统计服务类
 * 
 * @author WorkHours
 * @since 2025-09-19
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class StatisticsService {

    private final UserMapper userMapper;
    private final RestRecordMapper restRecordMapper;
    private final HolidayMapper holidayMapper;

    /**
     * 计算用户工作和休息统计
     * 
     * @param userId 用户ID
     * @param request 统计请求参数
     * @return 统计结果
     */
    public StatisticsVO calculateStatistics(Long userId, StatisticsRequest request) {
        log.info("开始计算用户{}的统计数据", userId);
        
        // 获取用户信息
        User user = userMapper.selectById(userId);
        if (user == null || user.getTripStartDate() == null) {
            throw new IllegalArgumentException("用户不存在或未设置出差开始日期");
        }
        
        // 确定统计日期范围
        LocalDate startDate = request.getStartDate() != null ? 
            LocalDate.parse(request.getStartDate()) : user.getTripStartDate();
        LocalDate endDate = request.getEndDate() != null ? 
            LocalDate.parse(request.getEndDate()) : LocalDate.now();
        
        if (startDate.isAfter(endDate)) {
            throw new IllegalArgumentException("开始日期不能晚于结束日期");
        }
        
        if (startDate.isBefore(user.getTripStartDate())) {
            throw new IllegalArgumentException("开始日期不能早于出差开始日期");
        }
        
        // 获取用户的休息记录
        List<RestRecord> restRecords = getUserRestRecords(userId, startDate, endDate);
        
        // 获取节假日数据
        Set<LocalDate> holidays = getHolidays(startDate, endDate, true);  // 节假日
        Set<LocalDate> workingHolidays = getHolidays(startDate, endDate, false);  // 调休工作日
        
        // 计算各项统计数据
        StatisticsVO statistics = new StatisticsVO();
        statistics.setStartDate(startDate.toString());
        statistics.setEndDate(endDate.toString());
        statistics.setIncludeTravel(request.getIncludeTravel());
        
        // 计算总天数
        long totalDays = ChronoUnit.DAYS.between(startDate, endDate) + 1;
        statistics.setTotalDays((int) totalDays);
        
        // 计算实际休息天数和车程天数
        RestDaysResult restDaysResult = calculateRestDays(restRecords, startDate, endDate, request.getIncludeTravel());
        statistics.setActualRestDays(restDaysResult.getActualRestDays());
        statistics.setTravelDays(restDaysResult.getTravelDays());
        
        // 计算应该休息天数（周末+节假日-调休工作日）
        int shouldRestDays = calculateShouldRestDays(startDate, endDate, holidays, workingHolidays);
        statistics.setShouldRestDays(shouldRestDays);
        
        // 计算工作天数
        int workDays = (int) totalDays - statistics.getActualRestDays();
        statistics.setWorkDays(workDays);
        
        // 计算欠休息天数
        int owedRestDays = shouldRestDays - statistics.getActualRestDays();
        statistics.setOwedRestDays(owedRestDays);
        
        log.info("统计计算完成: 总天数={}, 工作天数={}, 休息天数={}, 应休息天数={}, 欠休息天数={}", 
                totalDays, workDays, statistics.getActualRestDays(), shouldRestDays, owedRestDays);
        
        return statistics;
    }

    /**
     * 获取用户在指定时间范围内的休息记录
     */
    private List<RestRecord> getUserRestRecords(Long userId, LocalDate startDate, LocalDate endDate) {
        QueryWrapper<RestRecord> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", userId);
        queryWrapper.and(wrapper -> 
            wrapper.le("start_date", endDate)
                   .and(w -> w.isNull("end_date").or().ge("end_date", startDate))
        );
        queryWrapper.orderByAsc("start_date");
        return restRecordMapper.selectList(queryWrapper);
    }

    /**
     * 获取指定时间范围内的节假日
     */
    private Set<LocalDate> getHolidays(LocalDate startDate, LocalDate endDate, boolean isHoliday) {
        Set<LocalDate> holidaySet = new HashSet<>();
        
        for (int year = startDate.getYear(); year <= endDate.getYear(); year++) {
            QueryWrapper<Holiday> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("year", year)
                       .eq("is_holiday", isHoliday)
                       .ge("date", startDate)
                       .le("date", endDate);
            
            List<Holiday> holidays = holidayMapper.selectList(queryWrapper);
            for (Holiday holiday : holidays) {
                if (!holiday.getDate().isBefore(startDate) && !holiday.getDate().isAfter(endDate)) {
                    holidaySet.add(holiday.getDate());
                }
            }
        }
        
        return holidaySet;
    }

    /**
     * 计算实际休息天数
     */
    private RestDaysResult calculateRestDays(List<RestRecord> restRecords, LocalDate startDate, LocalDate endDate, boolean includeTravel) {
        Set<LocalDate> restDates = new HashSet<>();
        Set<LocalDate> travelDates = new HashSet<>();
        
        for (RestRecord record : restRecords) {
            LocalDate recordStartDate = record.getStartDate().isBefore(startDate) ? startDate : record.getStartDate();
            LocalDate recordEndDate = record.getEndDate() != null ? 
                (record.getEndDate().isAfter(endDate) ? endDate : record.getEndDate()) : endDate;
            
            if (recordStartDate.isAfter(endDate) || recordEndDate.isBefore(startDate)) {
                continue; // 记录不在统计范围内
            }
            
            // 计算这个休息记录的所有日期
            LocalDate current = recordStartDate;
            boolean isFirstDay = current.equals(record.getStartDate());
            boolean isLastDay = current.equals(record.getEndDate());
            
            while (!current.isAfter(recordEndDate)) {
                boolean isTravelDay = (isFirstDay && current.equals(record.getStartDate())) || 
                                    (isLastDay && current.equals(record.getEndDate()) && record.getEndDate() != null);
                
                if (isTravelDay) {
                    travelDates.add(current);
                    if (includeTravel) {
                        restDates.add(current);
                    }
                } else {
                    restDates.add(current);
                }
                
                current = current.plusDays(1);
                isFirstDay = false;
                isLastDay = current.equals(record.getEndDate());
            }
        }
        
        RestDaysResult result = new RestDaysResult();
        result.setActualRestDays(restDates.size());
        result.setTravelDays(travelDates.size());
        return result;
    }

    /**
     * 计算应该休息天数（周末+节假日-调休工作日）
     */
    private int calculateShouldRestDays(LocalDate startDate, LocalDate endDate, Set<LocalDate> holidays, Set<LocalDate> workingHolidays) {
        int shouldRestDays = 0;
        
        LocalDate current = startDate;
        while (!current.isAfter(endDate)) {
            // 如果是调休工作日，不算休息日
            if (workingHolidays.contains(current)) {
                current = current.plusDays(1);
                continue;
            }
            
            // 如果是节假日，算休息日
            if (holidays.contains(current)) {
                shouldRestDays++;
            } 
            // 如果是周末，算休息日
            else if (current.getDayOfWeek() == DayOfWeek.SATURDAY || current.getDayOfWeek() == DayOfWeek.SUNDAY) {
                shouldRestDays++;
            }
            
            current = current.plusDays(1);
        }
        
        return shouldRestDays;
    }

    /**
     * 内部类：休息天数计算结果
     */
    private static class RestDaysResult {
        private int actualRestDays;
        private int travelDays;
        
        public int getActualRestDays() { return actualRestDays; }
        public void setActualRestDays(int actualRestDays) { this.actualRestDays = actualRestDays; }
        public int getTravelDays() { return travelDays; }
        public void setTravelDays(int travelDays) { this.travelDays = travelDays; }
    }
}
